/**
编写一个类，它允许获取和设置键-值对，并且每个键都有一个 **过期时间** 。
该类有三个公共方法：
(1)set(key, value, duration) ：接收参数为整型键 key 、整型值 value 和以毫秒为单位的持续时间 duration 。
一旦 duration 到期后，这个键就无法访问。如果相同的未过期键已经存在，该方法将返回 true ，否则返回 false 
如果该键已经存在，则它的值和持续时间都应该被覆盖。
(2)get(key) ：如果存在一个未过期的键，它应该返回这个键相关的值。否则返回 -1 。
(3)count() ：返回未过期键的总数。

示例 1：
输入： 
actions = ["TimeLimitedCache", "set", "get", "count", "get"]
values = [[], [1, 42, 100], [1], [], [1]]
timeDeays = [0, 0, 50, 50, 150]
输出： [null, false, 42, 1, -1]
解释：
在 t=0 时，缓存被构造。
在 t=0 时，添加一个键值对 (1: 42) ，过期时间为 100ms 。因为该值不存在，因此返回false。
在 t=50 时，请求 key=1 并返回值 42。
在 t=50 时，调用 count() ，缓存中有一个未过期的键。
在 t=100 时，key=1 到期。
在 t=150 时，调用 get(1) ，返回 -1，因为缓存是空的。
 */

/**
 * !方案一：class语法+setTimeout/clearTimeout
 * 类相关的知识： 
 * （1）属性在constructor外 原型的属性
 * （2）属性在constructor内 实例的属性
 */
class TimeLimitedCache {
  cache = new Map(); // 类属性不需要const修饰
  set(key, value, duration) {
    const valInCache = this.cache.get(key); //是否存在同key
    valInCache && clearTimeout(valInCache.timer); //清除原有的计时器
    const timer = setTimeout(()=> this.cache.delete(key), duration) //设置新的计时器
    this.cache.set(key, {value, timer})
    return !!valInCache;
  }
  get(key) {
    return this.cache.get(key)?.value || -1;
  }
  count() {
    return this.cache.size;
  }
}
/**
 *!方案一: 原型（函数语法）+setTimeout/clearTimeout
 */
var TimeLimitedCache = function() {
  this.cache = new Map();  
};
TimeLimitedCache.prototype.set = function(key, value, duration) {
  const valInCache = this.cache.get(key);
  valInCache && clearTimeout(valInCache.timer);
  const timer = setTimeout(()=>this.cache.delete(key), duration)
  this.cache.set(key,{value,timer});
  return !!valInCache;
};
TimeLimitedCache.prototype.get = function(key) {
  return this.cache.get(key)?.value || -1;
};
TimeLimitedCache.prototype.count = function() {
  return this.cache.size;  
};

/**
 * todo 类的class写法和函数写法
 */

/**
 *! 方案二：维护到期时间
 * 存储时间，更新时间
 * 优点没有使用计时器 提升了性能
 * 缺点：过期了也会存在内存中，会被视为内存泄漏
 * 计时器的性能真的很差吗？？
 */
class TimeLimitedCache {
  cache = {};
  set(key, value, duration) {
    const hasUnexpiredValue = key in this.cache && this.cache[key].expiration > Date.now();
    this.cache[key] = {
      value,
      expiration: Date.now() + duration,
    };
    return hasUnexpiredValue;
  }
  get(key) {
    return key in this.cache && this.cache[key].expiration > Date.now() ? this.cache[key].value : -1;
  }
  count() {
    return Object.values(this.cache).fillter(({expiration}) => expiration > Date.now()).length;
  }
}

/**
 *!方案三 维护到期时间+优先级队列
 * 解决内存泄漏的问题
 */
const { MinPriorityQueue } = require('@datastructures-js/priority-queue');
class TimeLimitedCache { 
  cache = 0;
  size = 0;
  queue = new MinPriorityQueue(); //优先级越低越靠前

  /**
   * 清除过期元素
   */
  handleExpiredData() {
    const now = Date.now();
    while(this.queue.size() > 0 && this.queue.front().priority < now) {
      const entry = this.queue.dequeue().element;//删除过期元素
      if(!entry.overwritten) { //未被覆盖 false并过期直接删除，后续set新建
        delete this.cache[entry.key];
        this.size--;
      }
    }
  }

  set(key,value,duration) {
    this.handleExpiredData();
    const hasVal = key in this.cache;
    if(hasVal) {
      this.cache[key].overwritten = true;
    } else {
      this.size++;
    }
    const expiration = Date.now() + duration;
    const entry = {key, value, expiration, overwritten:false};
    this.cache[key] = entry;
    this.queue.enqueue(entry, expiration); //expiration 左右优先级，先过期的优先级靠前
    return hasVal;
  };
  get(key) {
    this.handleExpiredData();
    return key in this.cache ? this.cache[key].value :-1;
  };
  count() {
    this.handleExpiredData();
    return this.size
  };
}
/**
 * 优先级队列 
 * MinPriorityQueue() 从小到大
 * MaxPriorityQueue() 从大到小
 * PriorityQueue(compareFn) 优先级需要提供自定义排序函数
 * 实例方法：
 * enqueue(element, priority)
 * dequeue() 移除并返回头部
 * front() 头部元素//{element:实际值, priority:优先级}
 * back() 尾部愿随
 * isEmpty()
 * size()
 * clear()
 * 
 */

/**
 *!方案4 Map + Generator
 * Map是可遍历的， object是不可遍历的
 */
class TimeLimitedCache {
  cache = new Map();
  /**
   * 生成器函数 *Fn()
   * 内部没有yield，next()会整体执行一遍
   */
  *handleExpiredData() {
    const now = Date.now();
    for(const [key, {expiration, overwritten}] of this.cache){
      if(!overwritten && expiration < now) {
        this.cache.delete(key);
      }
    }
  }

  set(key, value, duration) {
    this.handleExpiredData().next();
    const hasVal = this.cache.has(key);
    hasVal && (this.cache.get(key).overwritten = true);
    this.cache.set(key, {
      value,
      expiration: Date.now() + duration,
      overwritten: false,
    });
    return hasVal;
  }

  get(key) {
    this.handleExpiredData().next();
    return this.cache.has(key) && this.cache.get(key).expiration > Date.now() ? this.cache.get(key).value : -1;
  }

  count() {
    this.handleExpiredData().next();
    return this.cache.size;
  }
}

/**
 * 生成器的用法
 */